#include <mex.h>
#include "gmatlab.h"

typedef struct
{
  GClosure closure;
  mxArray *func;
  mxArray *data;
  gboolean isvoid;
} MClosure;

static void mclosure_invalidate(gpointer notify_data, GClosure *closure)
{
  MClosure *mclosure = (MClosure*)closure;
  mxDestroyArray(mclosure->func);
  if (mclosure->data) mxDestroyArray(mclosure->data);
  mclosure->func = NULL;
  mclosure->data = NULL;
}

static void closure_dispose(gpointer closure)
{
  mclosure_invalidate(NULL, closure);
}

/* 
 * Note: If we call g_matlab_error() here, the gobject signal emission
 * mechanism leak a reference to the invocation instance as well as the
 * storage space for the parameters (and perhaps references to parameter
 * objects).  Instead, if there's an error, just use g_critical().
 */
static void mclosure_marshal(GClosure *closure, GValue *ret, guint nparam,
                             GValue *args, gpointer invocation_hint,
                             gpointer marshal_data)
{
  MClosure *mclosure = (MClosure*)closure;
  mxArray **mxArgs, *mxRet, *mxErr;
  GValue ret_try = {0, };
  guint i, j;

  g_return_if_fail(mclosure->func != NULL);
  if (mclosure->isvoid && !G_VALUE_HOLDS(ret, G_TYPE_NONE))
  {
    g_critical("mclosure_marshal: Closure is void, but %s return value requested", G_VALUE_TYPE_NAME(ret));
    return;
  }
  
  mxArgs = g_new(mxArray*, nparam+2);
  g_matlab_cleanup_add(g_free, mxArgs);
  
  j=0;
  mxArgs[j++] = mclosure->func;
  for (i=0; i < nparam; i++)
    mxArgs[j++] = g_value_to_mxArray(args+i);
  if (mclosure->data)
    mxArgs[j++] = mclosure->data;

  // Execution must not be interrupted until g_matlab_cleanup_pop().
  g_matlab_cleanup_push();
  mxErr = mexCallMATLABWithTrap(mclosure->isvoid ? 0 : 1, &mxRet,
                                j, mxArgs, "feval");
  g_matlab_cleanup_pop();
  if (mxErr) // mexCallMATLAB(0, NULL, 1, &mxErr, "rethrow");
  {
    /* It'd be nice to be able simply to pass along the exception.
     * Perhaps in the future, we could have an exception hook someplace and
     * rethrow the stored exception at g_matlab_cleanup(). */
    mxArray *errStr;
    g_warning("Exception occurred while executing signal callback.");
    mexCallMATLAB(1, &errStr, 1, &mxErr, "getReport");
    mexCallMATLAB(0, NULL, 1, &errStr, "disp");
    mxDestroyArray(errStr);
    mxDestroyArray(mxErr);
  }
  
  if (mclosure->isvoid) return;
  g_value_from_mxArray(&ret_try, mxRet);
  if (g_value_type_transformable(G_VALUE_TYPE(&ret_try), G_VALUE_TYPE(ret)))
  {
    g_value_transform(&ret_try, ret);
    g_value_unset(&ret_try);
  } else {
    GType gtype = G_VALUE_TYPE(&ret_try);
    g_value_unset(&ret_try);
    g_critical("mclosure_marshal: Unable to transform return value from %s to requested type %s", g_type_name(gtype), G_VALUE_TYPE_NAME(ret));
  }
}

/**
 * g_closure_matlab_new:
 * @func: (transfer none): a matlab function handle
 * @user_data: (transfer none) (allow-none): an #mxArray to be passed as user data to @func
 *
 * Returns: (transfer full): a new #GClosure
 */
GClosure *g_closure_matlab_new(mxArray *func, mxArray *user_data)
{
  GClosure *closure;
  MClosure *mclosure;
  GArgument num;
  mxArray *mxNum;
  
  g_return_val_if_fail(func != NULL, NULL);
  
  if (mxGetClassID(func) != mxFUNCTION_CLASS) g_matlab_error("gmatlab:badType", "g_closure_matlab_new: Arg 1 must be a function handle, not %s", mxGetClassName(func));
  mexCallMATLAB(1, &mxNum, 1, &func, "nargout");
  g_return_val_if_fail(arg_set_from_mxArray(&num, GI_TYPE_TAG_INT32, mxGetData(mxNum), mxGetClassID(mxNum), 0), NULL);
  mxDestroyArray(mxNum);
  if (num.v_int32 < 0 || num.v_int32 > 1) g_matlab_error("gmatlab:badType", "g_closure_matlab_new: Callback must have 0 or 1 output (not %d).", num.v_int32);
  
  // Note: closure will leak if there is an error during mxDuplication below
  closure = g_closure_new_simple(sizeof(MClosure), NULL);
  mclosure = (MClosure*)closure;
 
  mclosure->func = mxDuplicateArray(func);
  mexMakeArrayPersistent(mclosure->func);
  if (user_data)
  {
    mclosure->data = mxDuplicateArray(user_data);
    mexMakeArrayPersistent(mclosure->data);
  } else mclosure->data = NULL;
  mclosure->isvoid = (num.v_int32 == 0);
  
  g_closure_add_invalidate_notifier(closure, NULL, mclosure_invalidate);
  g_closure_set_marshal(closure, mclosure_marshal);

  return closure;
}

/**
 * g_signal_matlab_connect:
 * @obj: instance to connect to
 * @signal: detailed signal name
 * @func: matlab callback function handle
 * @data: (allow-none): user data for @func
 * @after: call after default handler?
 *
 * Connects @func to the emission of @signal by @obj.
 */
void g_signal_matlab_connect(GObject *obj, const gchar *signal, mxArray *func,
                             mxArray *data, gboolean after)
{
  GClosure *closure;
  guint sigid;
  gulong handler;
  GQuark detail = 0;
  
  g_return_if_fail(G_IS_OBJECT(obj));
  g_return_if_fail(signal != NULL);
  
  if (!g_signal_parse_name(signal, G_OBJECT_TYPE(obj), &sigid, &detail, TRUE))
    g_matlab_error("gmatlab:unknown", "g_signal_matlab_connect: Unknown signal %s for %s", signal, g_type_name(G_OBJECT_TYPE(obj)));
  
  // Note: closure will leak if there is an error connecting to the signal
  closure = g_closure_matlab_new(func, data);
  handler = g_signal_connect_closure_by_id(obj, sigid, detail, closure, after);
  
  // return handler;
}

/*
 * g_closure_matlab_invoke:
 * @closure: the #GClosure to invoke
 * @args: an #mxArray cell array of arguments
 *
 * Note: Uses g_matlab_escape_add(), so caller must invoke
 * g_matlab_escape_init() and follow up with g_matlab_escape_clear().
 *
 * Returns: the invocation return value
 */
/*
mxArray *g_closure_matlab_invoke(GClosure *closure, mxArray *mxArgs)
{
  mxArray *mxRet;
  GValue *ret, *args = NULL;
  guint i, num;
  
  g_return_val_if_fail(closure != NULL, NULL);
  g_return_val_if_fail(mxArgs != NULL, NULL);
  
  num = mxGetNumberOfElements(mxArgs);
  args = g_new0(GValue, num+1);
  g_matlab_cleanup_add(g_free, args);
  ret = args+num;  // Return value is last argument
  
  for (i=0; i < num; i++)
    g_matlab_value_from_mxArray(args+i, mxGetCell(mxArgs, i));
  
  g_closure_invoke(closure, ret, num, args, NULL);
  
  if (G_IS_VALUE(ret))
  {
    mxRet = g_matlab_value_to_mxArray(ret);
    g_value_unset(ret);
  }
  else mxRet = mxCreateCellMatrix(1, 0);
  
  return mxRet;
}
*/
